﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Web;
using System.Web.Caching;
using SWCC = System.Web.Caching.Cache;
using System.Diagnostics.CodeAnalysis;
using System.Threading;


namespace Medianamik.Core.Cache
{
    public abstract class CacheManagerBase<ManagerType> where ManagerType : CacheManagerBase<ManagerType>, new()
    {
        private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

        public static ManagerType Instance
        {
            get
            {
                return Singleton<ManagerType>.Instance;
            }
        }

        protected abstract string CacheKeyPrefix { get; }

        protected virtual TimeSpan DefaultItemLifetime
        {
            get
            {
                return TimeSpan.FromMinutes(5);
            }
        }

        protected virtual string AlterCacheKey(string pCacheKey)
        {
            return pCacheKey;
        }

        protected virtual CacheDependency GetDefaultCacheDependency()
        {
            return null;
        }

        private List<string> _cacheKeys = new List<string>();

        /// <summary>
        /// Gets an object from the cache or recreate it if it does not exist
        /// </summary>
        /// <param name="pCacheKey">The cache key</param>
        /// <param name="pAbsoluteExpiration"></param>
        /// <param name="pSlidingExpiration"></param>
        /// <param name="pSourceDataDelegate">The delegate to recreate the object</param>
        /// <returns>The requested object</returns>
        public T Get<T>(string pCacheKey, DateTime pAbsoluteExpiration, TimeSpan pSlidingExpiration, CacheDependency pDependency, CacheItemPriority pPriority, Func<T> pSourceDataDelegate)
        {
            _lock.EnterUpgradeableReadLock();
            try
            {
                var fullKey = GetFullCacheKey(pCacheKey);
                object obj = HttpRuntime.Cache.Get(fullKey);
                if (obj == null)
                {
                    obj = pSourceDataDelegate();
                    if (obj != null)
                    {
                        _lock.EnterWriteLock();
                        try
                        {
                            HttpRuntime.Cache.Insert(fullKey, obj, pDependency ?? GetDefaultCacheDependency(), pAbsoluteExpiration, pSlidingExpiration, pPriority, null);

                            if (!_cacheKeys.Contains(fullKey))
                            {
                                _cacheKeys.Add(fullKey);
                            }
                        }
                        finally
                        {
                            _lock.ExitWriteLock();
                        }
                    }
                }

                return (T)obj;
            }
            catch (InvalidCastException e)
            {
                throw new InvalidCastException(string.Concat("Unable to cast CacheItem '", pCacheKey, "'."), e);
            }
            finally
            {
                _lock.ExitUpgradeableReadLock();
            }
        }

        public virtual T Get<T>(string pCacheKey, DateTime pAbsoluteExpiration, Func<T> pSourceDataDelegate)
        {
            return Get(pCacheKey, pAbsoluteExpiration, SWCC.NoSlidingExpiration, null, CacheItemPriority.Default, pSourceDataDelegate);
        }

        public virtual T Get<T>(string pCacheKey, TimeSpan pSlidingExpiration, Func<T> pSourceDataDelegate)
        {
            return Get(pCacheKey, SWCC.NoAbsoluteExpiration, pSlidingExpiration, null, CacheItemPriority.Default, pSourceDataDelegate);
        }

        public virtual T Get<T>(string pCacheKey, CacheDependency pDependency, Func<T> pSourceDataDelegate)
        {
            return Get(pCacheKey, SWCC.NoAbsoluteExpiration, SWCC.NoSlidingExpiration, pDependency, CacheItemPriority.Default, pSourceDataDelegate);
        }

        public virtual T Get<T>(string pCacheKey, Func<T> pSourceDataDelegate)
        {
            return Get(pCacheKey, DateTime.Now.Add(DefaultItemLifetime), SWCC.NoSlidingExpiration, null, CacheItemPriority.Default, pSourceDataDelegate);
        }


        public void Clear()
        {
            _lock.EnterWriteLock();
            try
            {
                _cacheKeys.ForEach(k => HttpRuntime.Cache.Remove(k));
                _cacheKeys.Clear();
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

        public object Remove(string pCacheKey)
        {
            var fullKey = GetFullCacheKey(pCacheKey);
            _lock.EnterReadLock();
            try
            {

                if (_cacheKeys.Contains(fullKey))
                {
                    return HttpRuntime.Cache.Remove(fullKey);
                }
                return null;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        private string GetFullCacheKey(string pPartialKey)
        {
            return CacheKeyPrefix + AlterCacheKey(pPartialKey);
        }
    }

    public sealed class CacheManager : CacheManagerBase<CacheManager>
    {
        protected override string CacheKeyPrefix
        {
            get { return "CacheManager"; }
        }
    }
}
